Metadata ve Metadata Pointer Genişletmesi
Özet
metadata pointer
genişletmesi, bir token mint'i doğrudan bir metadata hesabına ilişkilendirir. Bu, metadata hesabının adresini mint'te saklayarak gerçekleşir. Bu metadata hesabı adresi, Metaplex gibi bir dış metadata hesabı olabilir veyametadata
genişletmesini kullanıyorsanız mint'in kendisi olabilir.metadata
mint genişletmesi, Token Genişletmeleri Programı aracılığıyla metadata'nın doğrudan mint hesaplarına gömülmesini sağlar. Bu her zaman kendine referans veren birmetadata pointer
ile birlikte gelir. Bu, minting aşamasında kapsamlı token bilgilerini gömmeyi kolaylaştırır.- Bu genişletmeler, metadata'nın nasıl ilişkilendirildiğini ve erişildiğini standartlaştırarak token'ların farklı uygulamalar ve platformlar arasındaki etkileşimini artırır.
- Metadata'yı doğrudan gömmek veya gösterim yapmak, ek sorgulara veya dış aramalara olan ihtiyacı azaltarak işlemleri ve etkileşimleri kolaylaştırabilir.
Genel Bakış
Token Genişletmeleri Programı, Solana'da metadata'yı kolaylaştırır. Token Genişletmeleri Programı olmadan, geliştiriciler, metadata'yı metadata hesaplarında bir metadata onchain programı kullanarak saklar; genellikle Metaplex
. Ancak, bunun bazı dezavantajları vardır. Örneğin, metadata'nın "bağlı olduğu" mint hesabı metadata hesabının bilincinde değildir. Bir hesabın metadata'ya sahip olup olmadığını belirlemek için mint ve Metaplex
programını birlikte PDA kaydetmemiz ve bir Metadata hesabının var olup olmadığını görmek için ağı sorgulamamız gerekiyor. Ek olarak, bu metadata'yı oluşturmak ve güncellemek için bir ikinci program (yani Metaplex
) kullanmanız gerekiyor. Bu süreçler, satıcı kilidi ve artan karmaşıklık getiriyor.
Token Genişletmeleri Programı'nın Metadata genişletmeleri, iki genişletme sunarak bunu düzeltir:
metadata-pointer
genişletmesi: Mint hesabında iki basit alan ekler: Token için metadata'yı tutan hesabın publicKey gösterimi Token-Metadata Arayüzü, ve bu göstergenin güncellenmesi için yetki.metadata
genişletmesi: Mint'in kendisinde metadata'yı saklamamıza olanak tanıyan alanları Token-Metadata Arayüzü tanımlar.
metadata
genişletmesi, başlıca mint'e geri işaret eden metadata-pointer
genişletmesi ile birlikte kullanılmalıdır.
Metadata-Pointer Genişletmesi:
Birden fazla metadata programı mevcut olduğundan, bir mint birçok hesabın mint'i tanımladığını iddia etmesine neden olur ve bu durum mint'in "resmi" metadata'sının hangisi olduğunu bilmekte karmaşıklık yaratır. Bunu çözmek için, metadata-pointer
genişletmesi mint hesabında metadataAddress
adında bir publicKey
alanı ekler. Bu, bu token için metadata'yı tutan hesabı işaret eder. Stabilcoin olmayı iddia eden taklit mint'lerinin önüne geçmek için, bir istemci artık mint ve metadata'nın birbirini işaret edip etmediğini kontrol edebilir.
Bu genişletme mint hesabına iki yeni alan ekler:
metadataAddress
: Bu token için metadata hesap adresini tutar;metadata
genişletmesini kullanıyorsanız kendisine de işaret edebilir.authority
: Metadata adresini belirleyebilecek yetki.
Genişletme ayrıca üç yeni yardımcı işlev tanıtır:
createInitializeMetadataPointerInstruction
createUpdateMetadataPointerInstruction
getMetadataPointerState
createInitializeMetadataPointerInstruction
işlevi, mint hesabında metadata adresini belirleyecek talimatı döndürür.
Bu işlev dört parametre alır:
mint
: oluşturulacak mint hesabıauthority
: metadata adresini belirleyebilecek yetkimetadataAddress
: metadata'yı tutan hesap adresiprogramId
: SPL Token program ID'si (bu durumda, Token Genişletme program ID'si olacaktır)
function createInitializeMetadataPointerInstruction(
mint: PublicKey,
authority: PublicKey | null,
metadataAddress: PublicKey | null,
programId: PublicKey,
);
createUpdateMetadataPointerInstruction
işlevi, mint hesabının metadata adresini güncelleyecek talimatı döndürür. Eğer yetkiye sahipseniz, metadata pointer'ını herhangi bir noktada güncelleyebilirsiniz.
Bu işlev beş parametre alır:
mint
: oluşturulacak mint hesabı.authority
: metadata adresini belirleyebilecek yetkimetadataAddress
: metadata'yı tutan hesap adresimultiSigners
: işlemi imzalayacak çoklu imzacılarprogramId
: SPL Token program ID'si (bu durumda, Token Genişletme program ID'si olacaktır)
function createUpdateMetadataPointerInstruction(
mint: PublicKey,
authority: PublicKey,
metadataAddress: PublicKey | null,
multiSigners: (Signer | PublicKey)[] = [],
programId: PublicKey = TOKEN_2022_PROGRAM_ID,
);
getMetadataPointerState
işlevi, belirli bir Mint
nesnesinin MetadataPointer
durumunu döndürür. Bunu getMint
işlevini kullanarak elde edebiliriz.
function getMetadataPointerState(mint: Mint): Partial<MetadataPointer> | null;
export interface MetadataPointer {
/** Metadata adresini belirleyebilecek isteğe bağlı yetki */
authority: PublicKey | null;
/** Metadata'yı tutan isteğe bağlı Hesap Adresi */
metadataAddress: PublicKey | null;
}
Metadata-pointer ile NFT Oluşturma
metadata-pointer
genişletmesi ile bir NFT oluşturmak için iki yeni hesaba ihtiyacımız vardır: mint
ve metadataAccount
.
mint
, genellikle Keypair.generate()
ile oluşturulan yeni bir Keypair
'dir. metadataAccount
, metadata mint genişletmesini kullanıyorsanız mint
'in publicKey
'si veya Metaplex gibi başka bir metadata hesabı olabilir.
Bu noktada, mint
yalnızca bir Keypair
'dir, ancak blok zincirinde yer için yer ayırmamız gerekiyor. Solana blok zincirindeki tüm hesaplar, hesabın boyutuna orantılı olarak kira ödemek zorundadır ve mint hesabının byte cinsinden ne kadar büyük olduğunu bilmemiz gerek. @solana/spl-token
kütüphanesinden getMintLen
yöntemini kullanabiliriz. Metadata-pointer genişletmesi, mint hesabının boyutunu iki yeni alan ekleyerek artırır: metadataAddress
ve authority
.
const mintLen = getMintLen([ExtensionType.MetadataPointer]);
const lamports = await connection.getMinimumBalanceForRentExemption(mintLen);
Metadata pointer ile mint
'i oluşturmak ve başlatmak için aşağıdaki sırayla birkaç talimata ihtiyacımız var:
- Blok zincirinde alan ayırtmak için
SystemProgram.createAccount
ilemint
hesabını oluştur. createInitializeMetadataPointerInstruction
ile metadata pointer genişletmesini başlat.createInitializeMintInstruction
ile mint'i başlat.
const createMintAccountInstructions = SystemProgram.createAccount({
fromPubkey: payer.publicKey,
lamports,
newAccountPubkey: mint.publicKey,
programId: TOKEN_2022_PROGRAM_ID,
space: mintLen,
});
const initMetadataPointerInstructions =
createInitializeMetadataPointerInstruction(
mint.publicKey,
payer.publicKey,
metadataAccount,
TOKEN_2022_PROGRAM_ID,
);
const initMintInstructions = createInitializeMintInstruction(
mint.publicKey,
decimals,
payer.publicKey,
payer.publicKey,
TOKEN_2022_PROGRAM_ID,
);
NFT'yi oluşturmak için talimatları bir işleme ekleyin ve Solana ağına gönderin:
const transaction = new Transaction().add(
createMintAccountInstructions,
initMetadataPointerInstructions,
initMintInstructions,
);
const sig = await sendAndConfirmTransaction(connection, transaction, [
payer,
mint,
]);
Metadata Genişletmesi:
metadata
genişletmesi, Token Genişletmeleri Programı'na heyecan verici bir eklemedir. Bu genişletme, metadata'yı doğrudan mint'in kendisine saklamamıza olanak tanır! Bu, ayrı bir hesaba olan ihtiyacı ortadan kaldırarak metadata yönetimini büyük ölçüde basitleştirir.
metadata
genişletmesi, metadata-pointer
genişletmesi ile doğrudan çalışır. Mint oluşturma sırasında, mint'e işaret eden metadata-pointer
genişletmesini de eklemelisiniz. Solana Token Genişletmeleri Programı dökümantasyonuna göz atın.
Metadata genişletmesindeki ek alanlar ve işlevler, Token-Metadata Arayüzü ı takip eder.
Bir mint, metadata genişletmesi ile başlatıldığında, bu ek alanları saklayacaktır:
type Pubkey = [u8; 32];
type OptionalNonZeroPubkey = Pubkey; // Eğer tüm sıfırsa, `None` olarak yorumlanır
pub struct TokenMetadata {
/// Metadata'yı güncellemek için imza atabilen yetki
pub update_authority: OptionalNonZeroPubkey,
/// Spoofing'i önlemek için kullanılan ilişkili mint
pub mint: Pubkey,
/// Token'ın uzun ismi
pub name: String,
/// Token için kısaltılmış sembol
pub symbol: String,
/// Daha zengin metadata'ya işaret eden URI
pub uri: String,
/// Token hakkında anahtar-değer çiftleri olarak herhangi bir ek metadata. Program, aynı anahtarı iki kez saklamaktan kaçınmalıdır.
pub additional_metadata: Vec<(String, String)>,
}
Bu ek alanlarla birlikte, @solana/spl-token-metadata
kütüphanesi aşağıdaki işlevlerle güncellendi:
createInitializeInstruction
createUpdateFieldInstruction
createRemoveKeyInstruction
createUpdateAuthorityInstruction
createEmitInstruction
pack
unpack
Ayrıca, @solana/spl-token kütüphanesi yeni bir işlev ve iki sabit tanıtır:
getTokenMetadata
LENGTH_SIZE
: verinin uzunluğunun sabit byte sayısıTYPE_SIZE
: verinin tipinin sabit byte sayısı
createInitializeInstruction
işlevi, hesabın içindeki metadata'yı başlatır ve birincil metadata alanlarını (isim, sembol, URI) ayarlar. İşlev daha sonra mint hesabında metadata alanlarını ayarlayacak bir talimat döndürür.
Bu işlev sekiz parametre alır:
mint
: başlatılacak mint hesabımetadata
: oluşturulacak metadata hesabımintAuthority
: token basabilen yetkiupdateAuthority
: metadata'yı güncellemek için imza atabilen yetkiname
: token'ın uzun ismisymbol
: token için kısaltılmış sembol, ayrıca ticker olarak da biliniruri
: daha zengin meta veriye işaret eden token URI'siprogramId
: SPL Token program ID'si (bu durumda, Token Genişletme program ID'si olacaktır)
export interface InitializeInstructionArgs {
programId: PublicKey;
metadata: PublicKey;
updateAuthority: PublicKey;
mint: PublicKey;
mintAuthority: PublicKey;
name: string;
symbol: string;
uri: string;
}
export function createInitializeInstruction(
args: InitializeInstructionArgs,
): TransactionInstruction;
createUpdateFieldInstruction
işlevi, bir token-metadata hesabındaki bir alanı oluşturacak veya güncelleyecek talimatı döndürür.
Bu işlev beş parametre alır:
metadata
: metadata hesap adresi.updateAuthority
: metadata'yı güncellemek için imza atabilecek yetkifield
: güncellenmek istenen alan, bu, ya yerleşikField
'lerden biri ya daadditional_metadata
alanında saklanan özel bir alan olabilirvalue
: alanın güncellenmiş değeriprogramId
: SPL Token program Id'si (bu durumda, Token Genişletme program Id'si olacaktır)
export enum Field {
Name,
Symbol,
Uri,
}
export interface UpdateFieldInstruction {
programId: PublicKey;
metadata: PublicKey;
updateAuthority: PublicKey;
field: Field | string;
value: string;
}
export function createUpdateFieldInstruction(
args: UpdateFieldInstruction,
): TransactionInstruction;
Güncellediğiniz metadata, başlangıçta tahsis edilen alandan daha fazla alan gerektiriyorsa, createUpdateFieldInstruction
'ın yeniden tahsis yapabilmesi için yeterli kira için bir system.transfer
eşleştirmeniz gerekiyor. Gerekli ek alanı getAdditionalRentForUpdatedMetadata
ile elde edebilirsiniz. Veya bu güncellemeyi bağımsız olarak çağırıyorsanız, tüm bunları bir arada yapmak için tokenMetadataUpdateFieldWithRentTransfer
yardımcı işlevini kullanabilirsiniz.
createRemoveKeyInstruction
işlevi, token-metadata hesabından additional_metadata
alanını kaldıracak talimatı döndürür.
Bu işlev beş parametre alır:
metadata
: metadata hesap adresiupdateAuthority
: metadata'yı güncellemek için imza atabilecek yetkifield
: kaldırmak istediğimiz alanprogramId
: SPL Token program ID'si (bu durumda, Token Genişletme program ID'si olacaktır)idempotent
: Doğru olduğunda, talimat anahtar mevcut değilse hata vermez
export interface RemoveKeyInstructionArgs {
programId: PublicKey;
metadata: PublicKey;
updateAuthority: PublicKey;
key: string;
idempotent: boolean;
}
export function createRemoveKeyInstruction(
args: RemoveKeyInstructionArgs,
): TransactionInstruction;
createUpdateAuthorityInstruction
işlevi, bir token-metadata hesabının yetkisini güncelleyecek talimatı döndürür.
Bu işlev dört parametre alır:
metadata
: metadata hesap adresioldAuthority
: metadata'yı güncellemek için imza atabilen mevcut yetkinewAuthority
: metadata'yı güncellemek için imza atabilecek yeni yetkiprogramId
: SPL Token program ID'si (bu durumda, Token Genişletme program ID'si olacaktır)
export interface UpdateAuthorityInstructionArgs {
programId: PublicKey;
metadata: PublicKey;
oldAuthority: PublicKey;
newAuthority: PublicKey | null;
}
export function createUpdateAuthorityInstruction(
args: UpdateAuthorityInstructionArgs,
): TransactionInstruction;
createEmitInstruction
"yayımlar" veya token-metadata'yı beklenen TokenMetadata durum formatında kaydeder. Bu, TokenMetadata arayüzünü takip etmek isteyen metadata programları için gerekli bir işlevdir. Yayımlama talimatı, indexleyicilerin ve diğer offchain kullanıcıların metadata'yı almak için çağrıda bulunmalarını sağlar. Ayrıca, özel metadata programlarının metadata'yı farklı bir formatta saklamasına izin verirken, Arayüz standartlarıyla uyumluluğu korumalarını sağlar.
Bu işlev dört parametre alır:
metadata
: metadata hesap adresiprogramId
: SPL Token program ID'si (bu durumda, Token Genişletme program ID'si olacaktır)start
: İsteğe bağlı metadata başlangıcıend
: İsteğe bağlı metadata sonu
export interface EmitInstructionArgs {
programId: PublicKey;
metadata: PublicKey;
start?: bigint;
end?: bigint;
}
export function createEmitInstruction(
args: EmitInstructionArgs,
): TransactionInstruction;
pack
işlevi, metadata'yı bir byte dizisine kodlar ve karşıtına unpack
, byte dizisinden metadata'yı çözer. Bu işlemler, metadata'nın byte boyutunu belirlemek için gereklidir; bu, yeterli depolama alanı tahsis etmek için kritiktir.
export interface TokenMetadata {
// Metadata'yı güncellemek için imza atabilen yetki
updateAuthority?: PublicKey;
// Spoofing'i önlemek için kullanılan ilişkili mint
mint: PublicKey;
// Token'ın uzun ismi
name: string;
// Token için kısaltılmış sembol
symbol: string;
// Daha zengin metadata'ya işaret eden URI
uri: string;
// Token hakkında anahtar-değer çiftleri olarak herhangi bir ek metadata
additionalMetadata: [string, string][];
}
export const pack = (meta: TokenMetadata): Uint8Array
export function unpack(buffer: Buffer | Uint8Array): TokenMetadata
getTokenMetadata
işlevi, belirli bir mint için metadata'yı döndürür.
Dört parametre alır:
connection
: Kullanılacak bağlantıaddress
: mint hesabıcommitment
: durum sorgulama için istenen taahhüt seviyesiprogramId
: SPL Token program hesabı (bu durumda, Token Genişletme program ID'si olacaktır)
export async function getTokenMetadata(
connection: Connection,
address: PublicKey,
commitment?: Commitment,
programId = TOKEN_2022_PROGRAM_ID,
): Promise<TokenMetadata | null>;
#### Metadata uzantısı ile NFT oluşturma
Metadata uzantısı ile NFT oluşturmak, birkaç ekstra adımla birlikte metadata-pointer ile oluşturmak gibidir:
1. Gerekli hesapları toplayın
2. Gerekli metadata boyutunu bulun/kararlaştırın
3. `mint` hesabını oluşturun
4. Pointer'ı başlatın
5. Mint'i başlatın
6. Mint hesabındaki metadata'yı başlatın
7. Gerekirse ek özel alanlar ekleyin
İlk olarak, `mint` bir Keypair olacak, genellikle `Keypair.generate()` kullanılarak oluşturulacaktır. Ardından, dahil edilecek metadata’yı belirlemeli ve toplam boyutu ve maliyeti hesaplamalıyız.
> **Önemli Not:** Mint hesabının boyutu metadata ve metadata-pointer uzantıları ile aşağıdakileri içerir:
>
> - Temel metadata alanları: isim, sembol ve URI
> - Bir metadata olarak saklamak istediğimiz ek özel alanlar
> - Gelecekte metadata’yı değiştirebilecek güncelleme yetkisi
> - `@solana/spl-token` kütüphanesindeki `LENGTH_SIZE` ve `TYPE_SIZE` sabitleri - bu, genellikle `getMintLen` çağrısıyla eklenen mint uzantılarıyla ilişkili boyutlardır, ancak metadata uzantısı değişken uzunlukta olduğu için manuel olarak eklenmelidir
> - Metadata pointer verisi (bu mint'in adresi olacak ve tutarlılık için yapılır)
> — **Bu bilgi kritik öneme sahiptir.**
<summary>**Ekstra Bilgi:**</summary>
Daha fazla metadatanız olacağını düşünüyorsanız, gerekli olan miktardan daha fazla alan ayırmaya gerek yoktur. `createUpdateFieldInstruction` otomatik olarak alanı yeniden tahsis edecektir! Ancak, mint hesabının yeterli kira alması için başka bir `system.transfer` işlemi eklemeniz gerekecektir.
Bunların tümünü program aracılığıyla belirlemek için `@solana/spl-token` kütüphanesinden `getMintLen` ve `pack` fonksiyonlarını kullanırız:
```ts
const metadata: TokenMetadata = {
mint: mint.publicKey,
name: tokenName,
symbol: tokenSymbol,
uri: tokenUri,
additionalMetadata: [["customField", "customValue"]],
};
const mintAndPointerLen = getMintLen([ExtensionType.MetadataPointer]); // Metadata uzantısı değişken uzunluktadır, bu yüzden aşağıda hesaplıyoruz
const metadataLen = TYPE_SIZE + LENGTH_SIZE + pack(metadata).length;
const totalLen = mintLen + mintAndPointerLen;
const lamports = await connection.getMinimumBalanceForRentExemption(totalLen);
Gerçekten mint
'i metadata ve metadata pointer ile oluşturmak ve başlatmak için belirli bir sırada birkaç talimata ihtiyacımız var:
SystemProgram.createAccount
ile blockchain'de alan rezerve edenmint
hesabını oluşturuncreateInitializeMetadataPointerInstruction
ile metadata pointer uzantısını başlatıncreateInitializeMintInstruction
ile mint'i kendisini başlatıncreateInitializeInstruction
ile metadata'yı başlatın (bu YALNIZCA temel metadata alanlarını ayarlar)- Opsiyonel:
createUpdateFieldInstruction
ile özel alanları ayarlayın (bir alan her çağrıda)
const createMintAccountInstructions = SystemProgram.createAccount({
fromPubkey: payer.publicKey,
lamports,
newAccountPubkey: mint.publicKey,
programId: TOKEN_2022_PROGRAM_ID,
space: totalLen,
});
const initMetadataPointerInstructions =
createInitializeMetadataPointerInstruction(
mint.publicKey,
payer.publicKey,
mint.publicKey, // metadata hesabı olarak mintin kendisine işaret edeceğiz
TOKEN_2022_PROGRAM_ID,
);
const initMintInstructions = createInitializeMintInstruction(
mint.publicKey,
decimals,
payer.publicKey,
payer.publicKey,
TOKEN_2022_PROGRAM_ID,
);
const initMetadataInstruction = createInitializeInstruction({
programId: TOKEN_2022_PROGRAM_ID,
mint: mint.publicKey,
metadata: mint.publicKey,
name: metadata.name,
symbol: metadata.symbol,
uri: metadata.uri,
mintAuthority: payer.publicKey,
updateAuthority: payer.publicKey,
});
const updateMetadataFieldInstructions = createUpdateFieldInstruction({
metadata: mint.publicKey,
updateAuthority: payer.publicKey,
programId: TOKEN_2022_PROGRAM_ID,
field: metadata.additionalMetadata[0][0],
value: metadata.additionalMetadata[0][1],
});
Tüm bu talimatları gömülü NFT'yi oluşturmak için bir işlemde sarın:
const transaction = new Transaction().add(
createMintAccountInstructions,
initMetadataPointerInstructions,
initMintInstructions,
initMetadataInstruction,
updateMetadataFieldInstructions, // herhangi bir özel alan eklemek istiyorsanız
);
const signature = await sendAndConfirmTransaction(connection, transaction, [
payer,
mint,
]);
Burada sıralamanın önemi vardır.
createUpdateFieldInstruction
yalnızca bir alanı günceller. Birden fazla özel alanınız olmasını istiyorsanız, bu yöntemi birden fazla kez çağırmanız gerekecektir. Ayrıca, temel metadata alanlarını güncellemek için aynı yöntemi kullanabilirsiniz:
const updateMetadataFieldInstructions = createUpdateFieldInstruction({
metadata: mint.publicKey,
updateAuthority: payer.publicKey,
programId: TOKEN_2022_PROGRAM_ID,
field: "name", // Alan | string
value: "new name",
});
Laboratuvar
Şimdiye kadar öğrendiklerimizle pratik yapma zamanı. Bu laboratuvar, metadata
ve metadata pointer
uzantıları ile NFT oluşturma sürecini gösteren bir betik oluşturacağız.
0. Başlarken
Hadi başlayalım ve başlangıç kodumuzu klonlayalım:
git clone https://github.com/Unboxed-Software/solana-lab-token22-metadata.git
cd solana-lab-token22-metadata
git checkout starter
npm install
Başlangıç dalında sağlananlara bir bakalım.
Gerekli tüm bağımlılıklarla başlatılan NodeJS projesinin yanı sıra, src/
dizininde iki diğer dosya sağlanmıştır.
cat.png
: NFT için kullanacağımız resimdir. Kendi resminizle değiştirmekten çekinmeyin.helpers.ts
index.ts
Irys kullanarak devnet üzerinde dosyaları yüklüyoruz; bu 100 KiB ile sınırlıdır.
helpers.ts
dosyası bize uploadOffChainMetadata
adında kullanışlı bir yardımcı işlev sağlar.
uploadOffChainMetadata
, Irys (eski adıyla Bundlr) kullanarak offchain metadata'yı Arweave'de saklamak için bir yardımcıdır. Bu laboratuvarda Token Extensions Program etkileşimine daha fazla odaklanacağız, bu nedenle bu yükleyici işlev sağlanmıştır. Bir NFT veya herhangi bir offchain metadata, NFT.storage, Solana'nın yerel ShadowDrive veya Irys (eski adıyla Bundlr) gibi herhangi bir depolama sağlayıcıda herhangi bir yere saklanabilir. Sonuçta, ihtiyacınız olan tek şey, barındırılan metadata json dosyasının URL'sidir.
Bu yardımcı, bazı dışa aktarılan arayüzlere sahiptir. Bu, işlevlerimizi temizleyecek.
export interface CreateNFTInputs {
payer: Keypair;
connection: Connection;
tokenName: string;
tokenSymbol: string;
tokenUri: string;
tokenAdditionalMetadata?: Record;
}
export interface UploadOffChainMetadataInputs {
tokenName: string;
tokenSymbol: string;
tokenDescription: string;
tokenExternalUrl: string;
tokenAdditionalMetadata?: Record;
imagePath: string;
metadataFileName: string;
}
index.ts
kodumuzu ekleyeceğimiz yerdir. Şu anda, connection
'ı ayarlayan ve kullanmamız için bir keypair başlatan kod bulunmaktadır.
Keypair payer
, tüm işlem boyunca gerekli olan her ödemenin sorumlusu olacaktır. payer
ayrıca mint yetkisi, mint dondurma yetkisi vb. gibi tüm yetkileri tutacaktır. Yetkiler için ayrı bir keypair kullanmak mümkün olsa da, basitlik açısından payer
kullanmaya devam edeceğiz.
Son olarak, bu laboratuvar devnet üzerinde gerçekleştirilecektir. Bu, metadata'yı Arweave'ye yüklemek için Irys kullanmamız gerektiğindendir - devnet veya mainnet bağlantısı gerektirir. Airdrop sorunlarıyla karşılaşırsanız:
initializeKeypair
'akeypairPath
parametresini ekleyin - yol, terminalinizdesolana config get
komutunu çalıştırarak alınabilir- Terminalinizde
solana address
komutunu çalıştırarak keypair'ın adresini alın - Adresi kopyalayın ve faucet.solana üzerinden devnet sol airdrop yapın.
1. Offchain metadata yükleme
Bu bölümde NFT metadata’mızı kararlaştıracak ve sağlanan yardımcı işlevleri kullanarak dosyalarımızı NFT.Storage'a yükleyeceğiz.
Offchain metadata’mızı yüklemek için önce NFT'mizi temsil edecek bir resim hazırlamalıyız. cat.png
sağladık, ancak bunu kendi resminizle değiştirmekten çekinmeyin. Çoğu resim türü, çoğu cüzdan tarafından desteklenmektedir. (Yine devnet Irys, dosya başına 100KiB'ye kadar izin verir)
Sonra, NFT'mizin hangi metadatalara sahip olacağına karar verelim. Kararlaştırdığımız alanlar name
, description
, symbol
, externalUrl
ve bazı attributes
(ek metadata) olacaktır. Birkaç kediyle ilgili metadata sağlayacağız, ancak kendi metadata'nızı oluşturmakta serbestsiniz.
name
: Cat NFTdescription
: Bu bir kedidirsymbol
: EMBexternalUrl
: https://solana.com/attributes
:{ species: 'Cat', breed: 'Cool' }
Son olarak, tüm bu verileri biçimlendirmemiz ve yüklenen metadata uri'sini almak için yardımcı işlevimiz uploadOffChainMetadata
'ya göndermemiz gerekmektedir.
Bunların hepsini bir araya getirdiğimizde, index.ts
dosyası aşağıdaki gibi görünecektir:
import { Connection } from "@solana/web3.js";
import { initializeKeypair } from "@solana-developers/helpers";
import { uploadOffChainMetadata } from "./helpers";
import dotenv from "dotenv";
dotenv.config();
const connection = new Connection(clusterApiUrl("devnet"), "finalized");
const payer = await initializeKeypair(connection, {
keypairPath: "your/path/to/keypair.json",
});
const imagePath = "src/cat.png";
const metadataPath = "src/temp.json";
const tokenName = "Cat NFT";
const tokenDescription = "This is a cat";
const tokenSymbol = "EMB";
const tokenExternalUrl = "https://solana.com/";
const tokenAdditionalMetadata = {
species: "Cat",
breed: "Cool",
};
const tokenUri = await uploadOffChainMetadata(
{
tokenName,
tokenDescription,
tokenSymbol,
imagePath,
metadataPath,
tokenExternalUrl,
tokenAdditionalMetadata,
},
payer,
);
// Yükleme işlemi tamamlandığında burada URI'yi güncelleyebilirsiniz
console.log("Token URI:", tokenUri);
Artık terminalinizde npm run start
komutunu çalıştırın ve kodunuzu test edin. Yükleme tamamlandığında, URI'yi kaydettiklerinizi görmelisiniz. Bağlantıyı ziyaret ettiğinizde, tüm offchain metadata'mızı içeren bir JSON nesnesi görmelisiniz.
2. NFT Fonksiyonu Oluşturma
Bir NFT oluşturmak, birden fazla talimatı içerir. Solana ağı ile etkileşimde bulunurken, bu talimatların tamamını tek bir işlemde birleştirmek en iyi uygulamadır çünkü işlemlerin atomik doğası vardır. Bu, tüm talimatların başarılı bir şekilde gerçekleştirilmesini veya hatalar durumunda tamamının geri alınmasını sağlar. Bu durumda, src/nft-with-embedded-metadata.ts
adında yeni bir dosyada createNFTWithEmbeddedMetadata
adlı yeni bir fonksiyon oluşturacağız.
Bu fonksiyon, bir NFT oluşturacak ve aşağıdaki adımları takip edecektir:
- Betimleme nesnesini oluştur
- Mint ayır
- Mintin kendisine işaret eden bir metadata-pointer'ı başlat
- Minti başlat
- Mint içindeki betimlemeyi başlat (bu, mint için isim, sembol ve uri ayarlayacaktır)
- Mintte ek betimlemeyi ayarlay
- İlgili token hesabını oluştur ve NFT'yi ona mintleyip mint yetkisini kaldır
- Tüm bunları tek bir işlemde toparla ve ağa gönder
- Çalıştığından emin olmak için token hesabını, mint hesabını ve betimlemeyi al ve yazdır
Bu yeni fonksiyon, helpers.ts
dosyasında tanımlanan CreateNFTInputs
alacaktır.
İlk adım olarak, src/nft-with-embedded-metadata.ts
adında yeni bir dosya oluşturalım ve aşağıdakileri ekleyelim:
import {
Keypair,
sendAndConfirmTransaction,
SystemProgram,
Transaction,
} from "@solana/web3.js";
import { CreateNFTInputs } from "./helpers";
import {
createInitializeInstruction,
createUpdateFieldInstruction,
pack,
TokenMetadata,
} from "@solana/spl-token-metadata";
import {
AuthorityType,
createAssociatedTokenAccountInstruction,
createInitializeMetadataPointerInstruction,
createInitializeMintInstruction,
createMintToCheckedInstruction,
createSetAuthorityInstruction,
ExtensionType,
getAccount,
getAssociatedTokenAddress,
getMint,
getMintLen,
getTokenMetadata,
LENGTH_SIZE,
TOKEN_2022_PROGRAM_ID,
TYPE_SIZE,
} from "@solana/spl-token";
export default async function createNFTWithEmbeddedMetadata(
inputs: CreateNFTInputs,
) {
const {
payer,
connection,
tokenName,
tokenSymbol,
tokenUri,
tokenAdditionalMetadata,
} = inputs;
// 0. Mint Ayarı
// 1. Betimleme nesnesini oluştur
// 2. Mint ayır
// 3. Mint'in kendisine işaret eden bir metadata-pointer'ı başlat
// 4. Minti başlat
// 5. Mint içindeki betimlemeyi başlat (bu, mint için isim, sembol ve uri ayarlayacaktır)
// 6. Mintte ek betimlemeyi ayarlay
// 7. İlgili token hesabını oluştur ve NFT'yi ona mintleyip mint yetkisini kaldır
// 8. Tüm bunları tek bir işlemde toparla ve ağa gönder
// 9. Çalıştığından emin olmak için token hesabını, mint hesabını ve betimlemeyi al ve yazdır
}
Şimdi adımları tek tek dolduralım.
- adım olarak mint'in anahtar çiftini oluşturalım, NFT'miz için ondalık olarak 0 ayarlayalım ve arzı 1 olarak belirleyelim.
// 0. Mint Ayarı
const mint = Keypair.generate();
const decimals = 0; // NFT'nin 0 ondalığı olmalı
const supply = 1; // NFT'lerin arzı 1 olmalı
Şimdi @solana/spl-token-metadata
arayüzünden TokenMetadata
nesnesini oluşturup, tüm girdilerimizi ona geçireceğiz.
Not: tokenAdditionalMetadata
geçişini yapmamız gerekiyor:
// 1. Betimleme nesnesini oluştur
const metadata: TokenMetadata = {
mint: mint.publicKey,
name: tokenName,
symbol: tokenSymbol,
uri: tokenUri,
// additionalMetadata: [['customField', 'customValue']],
additionalMetadata: Object.entries(tokenAdditionalMetadata || []).map(
([key, value]) => [key, value],
),
};
Artık SystemProgram.createAccount
kullanarak ilk on-chain talimatımızı oluşturabiliriz. Bunu yapmak için NFT'mizin mint hesabının boyutunu bilmemiz gerekiyor. Unutmayın, NFT'miz için iki uzantı kullanıyoruz: metadata pointer
ve metadata
uzantıları. Ayrıca, metadata 'gömülü' olarak metadata uzantısını kullandığı için değişken uzunluktadır. Bu nedenle, son uzunluğumuzu elde etmek için getMintLen
, pack
ve bazı sabit miktarları birleştiriyoruz.
Sonra, hesap oluşturmanın ne kadara mal olacağını görmek için getMinimumBalanceForRentExemption
çağrısını yapıyoruz.
Son olarak, SystemProgram.createAccount
fonksiyonuna her şeyi koyarak ilk talimatımızı alıyoruz:
// 2. Mint ayır
const mintLen = getMintLen([ExtensionType.MetadataPointer]);
const metadataLen = TYPE_SIZE + LENGTH_SIZE + pack(metadata).length;
const lamports = await connection.getMinimumBalanceForRentExemption(
mintLen + metadataLen,
);
const createMintAccountInstruction = SystemProgram.createAccount({
fromPubkey: payer.publicKey,
lamports,
newAccountPubkey: mint.publicKey,
programId: TOKEN_2022_PROGRAM_ID,
space: mintLen,
});
Adım 3, metadata pointer
uzantısını başlatmamızı gerektiriyor. Bunu, metadata hesabının mint'e işaret ettiğini kontrol ederek createInitializeMetadataPointerInstruction
fonksiyonunu çağırarak yapalım.
// 3. Mint'in kendisine işaret eden metadata-pointer'ı başlat
const initMetadataPointerInstruction =
createInitializeMetadataPointerInstruction(
mint.publicKey,
payer.publicKey,
mint.publicKey, // Metadata hesabı - kendisine işaret ediyor
TOKEN_2022_PROGRAM_ID,
);
Sonraki adım createInitializeMintInstruction
. Not edin ki bunu, betimlemeyi başlatmadan önce yapıyoruz.
// 4. Minti başlat
const initMintInstruction = createInitializeMintInstruction(
mint.publicKey,
decimals,
payer.publicKey,
payer.publicKey,
TOKEN_2022_PROGRAM_ID,
);
Artık createInitializeInstruction
ile betimlememizi başlatabiliriz. Tüm NFT betimlemelerimizi geçiyoruz, ancak tokenAdditionalMetadata
hariç, bu bir sonraki adımda ele alınacak.
// 5. Mint içindeki betimlemeyi başlat
const initMetadataInstruction = createInitializeInstruction({
programId: TOKEN_2022_PROGRAM_ID,
mint: mint.publicKey,
metadata: mint.publicKey,
name: metadata.name,
symbol: metadata.symbol,
uri: metadata.uri,
mintAuthority: payer.publicKey,
updateAuthority: payer.publicKey,
});
NFT'mizde tokenAdditionalMetadata
bulunmakta ve bir önceki adımda bunun createInitializeInstruction
ile ayarlanamayacağını gördük. Bu nedenle, her yeni ek alanı ayarlamak için bir talimat oluşturmamız gerekiyor. Bunu tokenAdditionalMetadata
içindeki her bir girdi için createUpdateFieldInstruction
çağırarak yapıyoruz.
// 6. Mintte ek betimlemeyi ayarlay
const setExtraMetadataInstructions = [];
for (const attributes of Object.entries(tokenAdditionalMetadata || [])) {
setExtraMetadataInstructions.push(
createUpdateFieldInstruction({
updateAuthority: payer.publicKey,
metadata: mint.publicKey,
field: attributes[0],
value: attributes[1],
programId: TOKEN_2022_PROGRAM_ID,
}),
);
}
Şimdi, bu NFT'yi kendimize mintleyelim ve ardından mint yetkisini iptal edelim. Bu, gerçek bir NFT olmaması durumunda yalnızca bir tane olacaktır. Bunu aşağıdaki fonksiyonlarla gerçekleştiriyoruz:
createAssociatedTokenAccountInstruction
createMintToCheckedInstruction
createSetAuthorityInstruction
// 7. İlgili token hesabını oluştur ve NFT'yi ona mintleyip mint yetkisini kaldır
const ata = await getAssociatedTokenAddress(
mint.publicKey,
payer.publicKey,
false,
TOKEN_2022_PROGRAM_ID,
);
const createATAInstruction = createAssociatedTokenAccountInstruction(
payer.publicKey,
ata,
payer.publicKey,
mint.publicKey,
TOKEN_2022_PROGRAM_ID,
);
const mintInstruction = createMintToCheckedInstruction(
mint.publicKey,
ata,
payer.publicKey,
supply, // NFT'lerin arzı 1 olmalı
decimals,
undefined,
TOKEN_2022_PROGRAM_ID,
);
// NFT'lerin mint yetkisi olmamalıdır, böylece kimse aynı NFT'den daha fazlasını mintleyemez
const setMintTokenAuthorityInstruction = createSetAuthorityInstruction(
mint.publicKey,
payer.publicKey,
AuthorityType.MintTokens,
null,
undefined,
TOKEN_2022_PROGRAM_ID,
);
Şimdi, tüm işlemlerimizi bir araya getirelim ve onu Solana ağına gönderelim. Burada sıralamanın önemli olduğunu belirtmek gerekir.
// 8. Tüm bunları tek bir işlemde toparla ve ağa gönder.
const transaction = new Transaction().add(
createMintAccountInstruction,
initMetadataPointerInstruction,
initMintInstruction,
initMetadataInstruction,
...setExtraMetadataInstructions,
createATAInstruction,
mintInstruction,
setMintTokenAuthorityInstruction,
);
const transactionSignature = await sendAndConfirmTransaction(
connection,
transaction,
[payer, mint],
);
Son olarak, NFT'miz hakkında tüm bilgileri alalım ve yazdıralım, böylece her şeyin çalıştığından emin oluruz.
// 9. Çalıştığından emin olmak için token hesabını, mint hesabını ve betimlemeyi al ve yazdır.
// Hesabı alma
const accountDetails = await getAccount(
connection,
ata,
"finalized",
TOKEN_2022_PROGRAM_ID,
);
console.log("İlişkili Token Hesabı =====>", accountDetails);
// Mint'i alma
const mintDetails = await getMint(
connection,
mint.publicKey,
undefined,
TOKEN_2022_PROGRAM_ID,
);
console.log("Mint =====>", mintDetails);
// Mint kendisinde metadataları depoladığı için, bunu böyle alabiliriz
const onChainMetadata = await getTokenMetadata(connection, mint.publicKey);
// Şimdi mint ile birlikte gelen metadata'yı görebiliriz
console.log("Onchain metadata =====>", onChainMetadata);
// Ve hatta şimdi offchain json'u alabiliriz
if (onChainMetadata?.uri) {
try {
const response = await fetch(onChainMetadata.uri);
const offChainMetadata = await response.json();
console.log("Mint offchain metadata =====>", offChainMetadata);
} catch (error) {
console.error("Offchain metadata alma veya ayrıştırma hatası:", error);
}
}
Tüm bunları bir araya getirdiğinizde src/nft-with-embedded-metadata.ts
içeriği aşağıdaki gibi olacaktır:
import {
Keypair,
sendAndConfirmTransaction,
SystemProgram,
Transaction,
} from "@solana/web3.js";
import { CreateNFTInputs } from "./helpers";
import {
createInitializeInstruction,
createUpdateFieldInstruction,
pack,
TokenMetadata,
} from "@solana/spl-token-metadata";
import {
AuthorityType,
createAssociatedTokenAccountInstruction,
createInitializeMetadataPointerInstruction,
createInitializeMintInstruction,
createMintToCheckedInstruction,
createSetAuthorityInstruction,
ExtensionType,
getAccount,
getAssociatedTokenAddress,
getMint,
getMintLen,
getTokenMetadata,
LENGTH_SIZE,
TOKEN_2022_PROGRAM_ID,
TYPE_SIZE,
} from "@solana/spl-token";
export default async function createNFTWithEmbeddedMetadata(
inputs: CreateNFTInputs,
) {
const {
payer,
connection,
tokenName,
tokenSymbol,
tokenUri,
tokenAdditionalMetadata,
} = inputs;
// 0. Mint Ayarı
const mint = Keypair.generate();
const decimals = 0; // NFT'nin 0 ondalığı olmalı
const supply = 1; // NFT'lerin arzı 1 olmalı
// 1. Betimleme nesnesini oluştur
const metadata: TokenMetadata = {
mint: mint.publicKey,
name: tokenName,
symbol: tokenSymbol,
uri: tokenUri,
// additionalMetadata: [['customField', 'customValue']],
additionalMetadata: Object.entries(tokenAdditionalMetadata || []).map(
([key, value]) => [key, value],
),
};
// 2. Mint ayır
const mintLen = getMintLen([ExtensionType.MetadataPointer]);
const metadataLen = TYPE_SIZE + LENGTH_SIZE + pack(metadata).length;
const lamports = await connection.getMinimumBalanceForRentExemption(
mintLen + metadataLen,
);
const createMintAccountInstruction = SystemProgram.createAccount({
fromPubkey: payer.publicKey,
lamports,
newAccountPubkey: mint.publicKey,
programId: TOKEN_2022_PROGRAM_ID,
space: mintLen,
});
// 3. Mint'in kendisine işaret eden metadata-pointer'ı başlat
const initMetadataPointerInstruction =
createInitializeMetadataPointerInstruction(
mint.publicKey,
payer.publicKey,
mint.publicKey, // Metadata hesabı - kendisine işaret ediyor
TOKEN_2022_PROGRAM_ID,
);
// 4. Minti başlat
const initMintInstruction = createInitializeMintInstruction(
mint.publicKey,
decimals,
payer.publicKey,
payer.publicKey,
TOKEN_2022_PROGRAM_ID,
);
// 5. Mint içindeki betimlemeyi başlat
const initMetadataInstruction = createInitializeInstruction({
programId: TOKEN_2022_PROGRAM_ID,
mint: mint.publicKey,
metadata: mint.publicKey,
name: metadata.name,
symbol: metadata.symbol,
uri: metadata.uri,
mintAuthority: payer.publicKey,
updateAuthority: payer.publicKey,
});
// 6. Mintte ek betimlemeyi ayarlay
const setExtraMetadataInstructions = [];
for (const attributes of Object.entries(tokenAdditionalMetadata || [])) {
setExtraMetadataInstructions.push(
createUpdateFieldInstruction({
updateAuthority: payer.publicKey,
metadata: mint.publicKey,
field: attributes[0],
value: attributes[1],
programId: TOKEN_2022_PROGRAM_ID,
}),
);
}
// 7. İlgili token hesabını oluştur ve NFT'yi ona mintleyip mint yetkisini kaldır
const ata = await getAssociatedTokenAddress(
mint.publicKey,
payer.publicKey,
false,
TOKEN_2022_PROGRAM_ID,
);
const createATAInstruction = createAssociatedTokenAccountInstruction(
payer.publicKey,
ata,
payer.publicKey,
mint.publicKey,
TOKEN_2022_PROGRAM_ID,
);
const mintInstruction = createMintToCheckedInstruction(
mint.publicKey,
ata,
payer.publicKey,
supply, // NFT'lerin arzı 1 olmalı
decimals,
undefined,
TOKEN_2022_PROGRAM_ID,
);
// NFT'lerin mint yetkisi olmamalıdır, böylece kimse aynı NFT'den daha fazlasını mintleyemez
const setMintTokenAuthorityInstruction = createSetAuthorityInstruction(
mint.publicKey,
payer.publicKey,
AuthorityType.MintTokens,
null,
undefined,
TOKEN_2022_PROGRAM_ID,
);
// 8. Tüm bunları tek bir işlemde toparla ve ağa gönder.
const transaction = new Transaction().add(
createMintAccountInstruction,
initMetadataPointerInstruction,
initMintInstruction,
initMetadataInstruction,
...setExtraMetadataInstructions, // Ek metadata alanlarını bozma
createATAInstruction,
mintInstruction,
setMintTokenAuthorityInstruction,
);
const transactionSignature = await sendAndConfirmTransaction(
connection,
transaction,
[payer, mint],
);
// 9. Çalıştığından emin olmak için token hesabını, mint hesabını ve betimlemeyi al ve yazdır.
// Hesabı alma
const accountDetails = await getAccount(
connection,
ata,
"finalized",
TOKEN_2022_PROGRAM_ID,
);
console.log("İlişkili Token Hesabı =====>", accountDetails);
// Mint'i alma
const mintDetails = await getMint(
connection,
mint.publicKey,
undefined,
TOKEN_2022_PROGRAM_ID,
);
console.log("Mint =====>", mintDetails);
// Mint kendisinde metadataları depoladığı için, bunu böyle alabiliriz
const onChainMetadata = await getTokenMetadata(connection, mint.publicKey);
// Şimdi mint ile birlikte gelen metadata'yı görebiliriz
console.log("Onchain metadata =====>", onChainMetadata);
// Ve hatta şimdi offchain JSON'u alabiliriz
if (onChainMetadata?.uri) {
try {
const response = await fetch(onChainMetadata.uri);
const offChainMetadata = await response.json();
console.log("Mint offchain metadata =====>", offChainMetadata);
} catch (error) {
console.error("Offchain metadata alma veya ayrıştırma hatası:", error);
}
}
}
3. NFT Fonksiyonunu Çağırma
Şimdi her şeyi src/index.ts
dosyasında bir araya getirelim.
src/index.ts
dosyasına geri dönün ve yeni oluşturduğumuz dosyadan createNFTWithEmbeddedMetadata
fonksiyonunu içe aktarın.
import createNFTWithEmbeddedMetadata from "./nft-with-embedded-metadata";
Ardından, ana fonksiyonun en sonunda çağırın ve gerekli parametreleri geçirin.
await createNFTWithEmbeddedMetadata({
payer,
connection,
tokenName,
tokenSymbol,
tokenUri,
});
src/index.ts
dosyası aşağıdaki gibi görünmelidir:
import { Connection } from "@solana/web3.js";
import { initializeKeypair, uploadOffChainMetadata } from "./helpers";
import createNFTWithEmbeddedMetadata from "./nft-with-embedded-metadata";
import dotenv from "dotenv";
dotenv.config();
const connection = new Connection("http://127.0.0.1:8899", "finalized");
const payer = await initializeKeypair(connection);
const imagePath = "NFT.png";
const tokenName = "NFT Adı";
const tokenDescription = "Bu harika bir Token Uzantısı NFT'dir";
const tokenSymbol = "TTT";
const tokenUri = await uploadOffChainMetadata({
connection,
payer,
tokenName,
tokenDescription,
tokenSymbol,
imagePath,
});
// URI'yi burada güncelleyebilir ve kodu çalıştırarak deneyebilirsiniz
console.log("Token URI:", tokenUri);
await createNFTWithEmbeddedMetadata({
payer,
connection,
tokenName,
tokenSymbol,
tokenUri,
});
Programı bir kez daha çalıştırmayı deneyin ve NFT'nizi ve metadata'yı görün.
npm run start
Başardınız! metadata
ve metadata pointer
uzantılarını kullanarak bir NFT oluşturdunuz.
Herhangi bir sorunla karşılaşırsanız, çözüm belgesine göz atabilirsiniz.
Meydan Okuma
Burada öğrendiklerinizi alarak kendi NFT veya SFT'nizi yaratın.